package com.luych.toolbox.base.service.service;

import com.luych.toolbox.base.service.constant.Constant;
import com.luych.toolbox.base.service.dao.*;
import com.luych.toolbox.base.service.entity.*;
import com.luych.toolbox.common.feign.param.base.service.RoleParam;
import com.luych.toolbox.common.feign.view.base.service.PermissionView;
import com.luych.toolbox.common.feign.view.base.service.RoleView;
import com.luych.toolbox.common.feign.view.base.service.UserView;
import com.luych.toolbox.common.page.PageParam;
import com.luych.toolbox.common.page.PageResult;
import com.luych.toolbox.common.service.base.BaseConvert;
import com.luych.toolbox.common.service.base.BaseService;
import com.luych.toolbox.common.service.exception.HttpException;
import com.luych.toolbox.common.service.page.SpringPageParamAdapter;
import com.luych.toolbox.common.service.page.SpringPageResultAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;

@Service
public class RoleService extends BaseService<RoleView, RoleParam, Role> {

    private static final BaseConvert<RoleParam, Role> roleParamToEntity = Role::new;
    private static final BaseConvert<Role, RoleView> roleEntityToView = RoleView::new;
    private static final BaseConvert<User, UserView> userEntityToView = UserView::new;
    private static final BaseConvert<Permission, PermissionView> permissionEntityToView = PermissionView::new;

    private static final String ADMIN = "adminRoleId";
    private final UserDao userDao;
    private final RoleDao roleDao;
    private final PermissionDao permissionDao;
    private final UserRRoleDao userRRoleDao;
    private final RoleRPermissionDao roleRPermissionDao;

    @Autowired
    public RoleService(UserDao userDao,
                       RoleDao roleDao,
                       PermissionDao permissionDao,
                       UserRRoleDao userRRoleDao,
                       RoleRPermissionDao roleRPermissionDao) {
        super(roleDao, roleParamToEntity, roleEntityToView);
        this.userDao = userDao;
        this.roleDao = roleDao;
        this.permissionDao = permissionDao;
        this.userRRoleDao = userRRoleDao;
        this.roleRPermissionDao = roleRPermissionDao;
    }

    @Override
    protected RoleView fill(RoleView view) {
        // get permissions
        List<Permission> permissions = new ArrayList<>();
        List<RoleRPermission> roleRPermissions = roleRPermissionDao.findByRoleId(view.getId());
        for (RoleRPermission roleRPermission : roleRPermissions) {
            Optional<Permission> permission = permissionDao.findById(roleRPermission.getPermissionId());
            permission.ifPresent(permissions::add);
        }
        view.setPermissions(permissions.stream().map(permissionEntityToView).collect(Collectors.toList()));
        // get users
        List<User> users = new ArrayList<>();
        List<UserRRole> userRRoles = userRRoleDao.findByRoleId(view.getId());
        for (UserRRole userRRole : userRRoles) {
            Optional<User> user = userDao.findById(userRRole.getUserId());
            user.ifPresent(users::add);
        }
        view.setUsers(users.stream().map(userEntityToView).collect(Collectors.toList()));
        // return
        return view;
    }

    @Override
    @Transactional
    public RoleView create(RoleParam param) {
        Assert.notNull(param.getRoleName(), "Param roleName can't be empty");
        Assert.notEmpty(param.getPermissionIds(), "Param permissionIds can't be empty");
        // check name exists
        if (roleDao.getByRoleName(param.getRoleName()).isPresent()) {
            throw new HttpException(HttpStatus.BAD_REQUEST, Constant.ROLE_SAVE_NAME_USED);
        }
        // check permission exists
        List<String> allPermissionIds = permissionDao.findAll().stream().map(Permission::getId).collect(Collectors.toList());
        List<String> notExistPermissionIds = param.getPermissionIds().stream()
                .filter(paramPermissionId -> !allPermissionIds.contains(paramPermissionId))
                .collect(Collectors.toList());
        if (!notExistPermissionIds.isEmpty()) {
            throw new RuntimeException("Can't find permission with id: " + String.join(",", notExistPermissionIds));
        }
        // save role
        Role role = roleDao.save(roleParamToEntity.apply(param));
        // save role permission
        for (String permissionId : param.getPermissionIds()) {
            RoleRPermission roleRPermission = new RoleRPermission();
            roleRPermission.setId(UUID.randomUUID().toString());
            roleRPermission.setRoleId(role.getId());
            roleRPermission.setPermissionId(permissionId);
            roleRPermissionDao.save(roleRPermission);
        }
        // final, return
        RoleView view = roleEntityToView.apply(role);
        this.fill(view);
        return view;
    }

    @Override
    @Transactional
    public RoleView modify(RoleParam param) {
        Assert.notNull(param.getRoleName(), "Param roleName can't be empty");

        // check name exists
        Optional<Role> roleOptOfName = roleDao.getByRoleName(param.getRoleName());
        if (roleOptOfName.isPresent() && !roleOptOfName.get().getId().equals(param.getId())) {
            throw new RuntimeException(Constant.ROLE_SAVE_NAME_USED);
        }
        // check permission exists if set
        if (param.getPermissionIds() != null) {
            List<String> allPermissionIds = permissionDao.findAll().stream().map(Permission::getId).collect(Collectors.toList());
            List<String> notExistPermissionIds = param.getPermissionIds().stream()
                    .filter(paramPermissionId -> !allPermissionIds.contains(paramPermissionId))
                    .collect(Collectors.toList());
            if (!notExistPermissionIds.isEmpty()) {
                throw new RuntimeException("Can't find permission with id: " + String.join(",", notExistPermissionIds));
            }
        }
        // save role
        Role role = roleDao.save(roleParamToEntity.apply(param));
        // if set permissionIds in param, then we update permission of role here
        if (param.getPermissionIds() != null) {
            List<String> oldPermissionIds = roleRPermissionDao.findByRoleId(role.getId()).stream()
                    .map(RoleRPermission::getPermissionId).collect(Collectors.toList());
            List<String> newPermissionIds = param.getPermissionIds();
            oldPermissionIds.stream().filter(oldPermissionId -> !newPermissionIds.contains(oldPermissionId))
                    .forEach(oldPermissionId -> roleRPermissionDao.deleteByRoleIdAndPermissionId(role.getId(), oldPermissionId));
            newPermissionIds.stream().filter(newPermissionId -> !oldPermissionIds.contains(newPermissionId))
                    .forEach(newPermissionId -> {
                        RoleRPermission roleRPermission = new RoleRPermission();
                        roleRPermission.setId(UUID.randomUUID().toString());
                        roleRPermission.setRoleId(role.getId());
                        roleRPermission.setPermissionId(newPermissionId);
                        roleRPermissionDao.save(roleRPermission);
                    });
        }
        // final, return
        RoleView view = roleEntityToView.apply(role);
        this.fill(view);
        return view;
    }

    @Override
    @Transactional
    public void remove(String id) {
        if (ADMIN.equals(id)) {
            throw new HttpException(HttpStatus.LOCKED, Constant.ROLE_DELETE_REJECT_ADMIN);
        }
        roleRPermissionDao.deleteByRoleId(id);
        super.remove(id);
    }

    public void addPermission(String roleId, String permissionId) {
        RoleRPermission roleRPermission = new RoleRPermission();
        roleRPermission.setId(UUID.randomUUID().toString());
        roleRPermission.setRoleId(roleId);
        roleRPermission.setPermissionId(permissionId);
        roleRPermissionDao.save(roleRPermission);
    }

    public void removePermission(String roleId, String permissionId) {
        Optional<RoleRPermission> roleRPermission = roleRPermissionDao.getByRoleIdAndPermissionId(roleId, permissionId);
        roleRPermission.ifPresent(roleRPermissionDao::delete);
    }

    public List<PermissionView> listPermission(String roleId) {
        List<RoleRPermission> roleRPermissions = roleRPermissionDao.findByRoleId(roleId);
        return roleRPermissions.stream().map(RoleRPermission::getPermissionId).map(permissionDao::findById)
                .filter(Optional::isPresent).map(Optional::get).map(permissionEntityToView).collect(Collectors.toList());
    }

    public PageResult<RoleView> pageByKeyword(PageParam pageParam, String keyword) {
        Page<Role> page;
        if (StringUtils.isEmpty(keyword)) {
            page = roleDao.findAll(new SpringPageParamAdapter(pageParam));
        } else {
            page = roleDao.findByRoleNameLike('%' + keyword + '%', new SpringPageParamAdapter(pageParam));
        }
        PageResult<Role> pageResult = new SpringPageResultAdapter<>(page);
        return pageResult.convert(roleEntityToView).convert(this::fill);
    }
}
